package com.wiscamp.ninechapters.problems.domain.services;

import com.wiscamp.ninechapters.common.exception.ServiceException;
import com.wiscamp.ninechapters.problems.domain.gateway.TagGateway;
import com.wiscamp.ninechapters.problems.domain.models.problems.problems.ProblemKnowledgePoint;
import com.wiscamp.ninechapters.problems.domain.models.problems.questions.Question;
import com.wiscamp.ninechapters.problems.domain.models.problems.questions.Solution;
import com.wiscamp.ninechapters.problems.domain.models.reports.*;
import com.wiscamp.ninechapters.problems.domain.models.sheets.Sheet;
import com.wiscamp.ninechapters.problems.domain.models.sheets.SheetDetail;
import com.wiscamp.ninechapters.problems.domain.repositories.*;
import com.wiscamp.ninechapters.problems.enums.AnswerTypes;
import com.wiscamp.ninechapters.problems.enums.SetTypes;
import com.wiscamp.ninechapters.problems.infrastructure.converters.ProblemConverter;
import com.wiscamp.ninechapters.problems.infrastructure.repositories.ProblemBuilder;
import cube.common.core.CollectionUtils;
import cube.common.core.JsonUtils;
import cube.ddd.aspect.HandleServiceException;
import cube.ddd.domain.BaseDomainService;
import jakarta.validation.constraints.Min;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Objects;
import java.util.stream.Collectors;

@Service
public class TaskReportService extends BaseDomainService {

    private final TaskReportRepository taskReportRepository;

    private final TaskRepository taskRepository;

    private final SheetRepository sheetRepository;

    private final ProblemSetRepository problemSetRepository;

    private final ProblemTypeRepository problemTypeRepository;

    private final QuestionTypeRepository questionTypeRepository;

    private final ProblemRepository problemRepository;

    private final ProblemBuilder problemBuilder;

    private final TagGateway tagGateway;

    public TaskReportService(TaskReportRepository taskReportRepository, TaskRepository taskRepository, SheetRepository sheetRepository, ProblemSetRepository problemSetRepository, ProblemRepository problemRepository, ProblemBuilder problemBuilder, ProblemTypeRepository problemTypeRepository, QuestionTypeRepository questionTypeRepository, TagGateway tagGateway) {
        this.taskReportRepository = taskReportRepository;
        this.taskRepository = taskRepository;
        this.sheetRepository = sheetRepository;
        this.problemSetRepository = problemSetRepository;
        this.problemRepository = problemRepository;
        this.problemBuilder = problemBuilder;
        this.problemTypeRepository = problemTypeRepository;
        this.questionTypeRepository = questionTypeRepository;
        this.tagGateway = tagGateway;
    }

    /**
     * Gets report of a practice task
     *
     * @param taskId
     * @return
     */
    @Cacheable(value = "reports", key = "#taskId")
    @HandleServiceException
    public TaskReport getReportByTask(@Min(0) long taskId) {
        TaskReport report;
        var content = taskReportRepository.getReportContent(taskId);
        if (Objects.isNull(content)) {
            // 如果数据库中没有查找到此前生成的任务报告，则新建任务报告
            report = createTaskReport(taskId);
            if (Objects.nonNull(report)) {
                content = new TaskReportContent();
                content.setTaskId(taskId);
                var json = JsonUtils.toJson(report);
                content.setReportContent(json);
                taskReportRepository.saveReportContent(content);
            }
        } else {
            // 如果数据库中已查找到此前生成的任务报告，则从数据库加载存储的任务报告json对象
            report = JsonUtils.toObject(content.getReportContent(), TaskReport.class);
        }
        return report;
    }

    private TaskReport createTaskReport(@Min(0) long taskId) {
        var report = new TaskReport();
        var task = taskRepository.getTask(taskId);
        if (task == null) {
            var errorCode = "service-exception.distribution-task.id.notfound";
            throw new ServiceException(null, errorCode, "distribution task id: " + taskId);
        }

        var set = problemSetRepository.getProblemSet(task.getSetId());
        if (set == null) {
            var errorCode = "service-exception.problemSet.id.notfound";
            throw new ServiceException(null, errorCode, "set id: " + taskId);
        }

        // 获取任务关联答题卡
        Sheet sheet = sheetRepository.getSheetByTask(taskId);
        if (sheet == null) {
            var errorCode = "service-exception.sheet.notfound";
            throw new ServiceException(null, errorCode,
                    "user id / set id: " + task.getUserId() + " / " + task.getSetId());
        }

        var problemIds = problemSetRepository.getProblemIdsBySet(task.getSetId());
        var problems = problemRepository.getProblemsByIds(problemIds);
        Collection<SheetDetail> sheetDetails = sheet.getDetails();
        int problemCounter = 0;
        int correctCount = 0;
        int wrongCount = 0;
        int skipCount = 0;
        // 根据练习任务中的题目及答题卡相应条目，生成每一条练习任务报告明细
        var problemIndex = 1;

        double totalGainScore = 0;
        for (var problem : problems) {
            problemCounter = problemCounter + 1;
            var reportProblem = new ReportProblem();
            reportProblem.setProblemId(problem.getProblemId());
            long problemTypeId = problem.getProblemTypeId();
            var problemType = problemTypeRepository.getProblemType(problemTypeId);
            reportProblem.setProblemIndex(problemIndex);
            problemIndex = problemIndex + 1;
            reportProblem.setDifficulty(problem.getCurrentDifficulty());
            reportProblem.setProblemTypeName(problemType.getTypeName());
            reportProblem.setTagNames(problem.getTagNames());
            if (problem.getCurrentStem() != null) {
                reportProblem.setStemId(problem.getStemId());
                reportProblem.setStemHypertext(problem.getCurrentStem().getStemHypertext());
            }

            Collection<Question> questions = problem.getCurrentQuestions();
            //TODO: 目前只支持简单题型报告，待完善
            var questionIndex = 1;

            double problemGainScore = 0;
            boolean allQuestionCorrect = true;

            var reportQuestions = new ArrayList<ReportQuestion>();
            var allQuestionSkipped = true;
            for (var question : questions) {
                var reportQuestion = new ReportQuestion();
                reportQuestion.setProblemId(question.getProblemId());
                reportQuestion.setQuestionId(question.getQuestionId());
                reportQuestion.setQuestionIndex(questionIndex);
                questionIndex = questionIndex + 1;
                reportQuestion.setQuestionHypertext(question.getQuestionHypertext());

                var solution = question.getPreferredSolution();
                if (solution == null) {
                    solution = (Solution) question.getSolutions().toArray()[0];
                }
                if (solution != null) {
                    reportQuestion.setSolutionHypertext(solution.getSolutionHypertext());
                    reportQuestion.setSolutionId(solution.getSolutionId());
                }

                if (question.isHasOptions()) {
                    reportQuestion.setHasOption(question.isHasOptions());
                    reportQuestion.setOptions(question.getCurrentOptions());
                }

                // 根据答题卡明细汇总答题报告
                var detail = sheetRepository.findSheetDetail(sheetDetails, question.getQuestionId());
                if (detail != null) {
                    int questionSpendSeconds = detail.getAnsweringSeconds();
                    reportQuestion.setQuestionSpendSeconds(questionSpendSeconds);
                    reportQuestion.setQuestionGainScore(detail.getGainScore());
                    problemGainScore = problemGainScore + detail.getGainScore();
                    reportQuestion.setCorrectStatus(detail.getCorrectStatus());
                    long questionTypeId = question.getQuestionTypeId();
                    var questionType = questionTypeRepository.getQuestionType(questionTypeId);
                    reportQuestion.setQuestionTypeName(questionType.getTypeName());
                    if (detail.getCorrectStatus() != 1) {
                        allQuestionCorrect = false;
                    }

                    var sheetAnswers = detail.getSheetAnswers();
                    var allAnswerSkipped = true;
                    for (var sheetAnswer : sheetAnswers) {
                        // 只要所有答案为skip，则问题设置为skip
                        if (!sheetAnswer.isSkipped()) allAnswerSkipped = false;

                        var reportAnswer = new ReportAnswer();
                        var correctAnswer = problemBuilder.getAnswer(sheetAnswer.getCorrectAnswerId());
                        reportAnswer.setAnswerIndex(sheetAnswer.getAnswerIndex());
                        reportAnswer.setCorrectAnswerId(sheetAnswer.getCorrectAnswerId());
                        reportAnswer.setCorrectAnswerTypeName(correctAnswer.getAnswerType().getName());
                        reportAnswer.setCorrectAnswer(correctAnswer.getAnswerHypertext());
                        reportAnswer.setUserAnswerTypeName(AnswerTypes.valueOf(sheetAnswer.getContentTypeId()).getName());
                        reportAnswer.setUserAnswer(sheetAnswer.getContentText());
                        reportAnswer.setCorrect(sheetAnswer.isCorrect());
                        reportAnswer.setSkipped(sheetAnswer.isSkipped());
                        reportAnswer.setAnswerGainScore(sheetAnswer.getGainScore());

                        reportQuestion.getReportAnswers().add(reportAnswer);
                    }

                    // region deleted code
                    /*//TODO: 练习任务报告需针对不同题型重新梳理
                    if (detail.isCorrect()) {
                        correctCount = correctCount + 1;
                    }
                    reportProblem.setSkipped(detail.isSkipped());
                    reportProblem.setCorrect(detail.isCorrect());
                    if (questionType.isHasOption()) {
                        // 选择题
                        Answer answer = question.getCurrentAnswer();
                        reportProblem.setHasOption(true);
                        reportProblem.setHasOption(true);

                        if (answer instanceof OptionAnswer) {
                            int userAnswerIndex = problemBuilder.getOptionIndex(question.getCurrentOptions(), detail.getContentId());
                            int correctAnswerIndex = problemBuilder.getOptionIndex(question.getCurrentOptions(), answer.getAnswerOptionId());
                            reportProblem.setUserAnswerIndex(userAnswerIndex);
                            reportProblem.setCorrectAnswerIndex(correctAnswerIndex);
                            reportProblem.setUserAnswer(problemBuilder.getOptionIndexPrefix(userAnswerIndex));
                            reportProblem.setCorrectAnswer(problemBuilder.getOptionIndexPrefix(correctAnswerIndex));
                        }
                        if (answer instanceof MultipleOptionAnswer) {
                            var userOptionIdsText = detail.getContentHypertext();
                            var correctOptionIdsText = answer.getAnswerHypertext();
                            var userOptionIds = ParseHelper.parseLong(userOptionIdsText, ",");
                            var correctOptionIds = ParseHelper.parseLong(correctOptionIdsText, ",");

                            var userAnswerPrefix = new ArrayList<String>();
                            for (var optionId : userOptionIds) {
                                int userAnswerIndex = problemBuilder.getOptionIndex(question.getCurrentOptions(), optionId);
                                var prefix = problemBuilder.getOptionIndexPrefix(userAnswerIndex);
                                userAnswerPrefix.add(prefix);
                            }
                            var correctAnswerPrefix = new ArrayList<String>();
                            for (var optionId : correctOptionIds) {
                                int correctAnswerIndex = problemBuilder.getOptionIndex(question.getCurrentOptions(), optionId);
                                var prefix = problemBuilder.getOptionIndexPrefix(correctAnswerIndex);
                                correctAnswerPrefix.add(prefix);
                            }
                            reportProblem.setUserAnswer(StringUtils.join(userAnswerPrefix, ","));
                            reportProblem.setCorrectAnswer(StringUtils.join(correctAnswerPrefix, ","));
                        }
                    } else {
                        // 计算题&填空题
                        boolean hasMultipleAnswers = question.isHasMultipleAnswers();
                        if (!hasMultipleAnswers) {
                            Answer answer = question.getCurrentAnswer();
                            var sheetAnswerText = detail.getContentHypertext().trim().replace("", "");
                            var correctAnswerText = answer.getAnswerHypertext().trim().replace("", "");
                            reportProblem.setUserAnswerIndex(0);
                            reportProblem.setCorrectAnswerIndex(0);
                            reportProblem.setCorrectAnswer(answer.getAnswerHypertext());
                            reportProblem.setUserAnswer(detail.getContentHypertext());
                        } else {
                            // 处理填空题
                            var answers = question.getCurrentAnswers();
                            reportProblem.setCorrectAnswers(answers);
                            Collection<SheetAnswer> textExtensions = sheetBuilder.getSheetTextExtensions(detail.getDetailId());
                            reportProblem.setSheetAnswers(textExtensions);
                        }
                    }
                    reportProblem.setSkipped(detail.isSkipped());*/
                    // endregion

                    //totalGainScore = totalGainScore + detail.getGainScore();
                    reportQuestions.add(reportQuestion);

                    if (!allAnswerSkipped)
                        allQuestionSkipped = false;
                }
            }

            // 一个题下的所有问题都正确视为全对
            if (allQuestionCorrect) {
                correctCount++;
                reportProblem.setCorrect(true);
            } else {
                wrongCount++;
            }

            if (allQuestionSkipped) {
                skipCount++;
                reportProblem.setSkipped(true);
            }

            reportProblem.setProblemGainScore(problemGainScore);
            totalGainScore = totalGainScore + problemGainScore;
            reportProblem.setReportQuestions(reportQuestions);
            // get points
            var primaryPoints = tagGateway.getPrimaryKnowledgePointsByProblem(problem.getProblemId(), true);
            var minorPoints = tagGateway.getPrimaryKnowledgePointsByProblem(problem.getProblemId(), false);
            var primaryPointNames = CollectionUtils.toArrayList(primaryPoints, ProblemConverter.Instance::toKnowledgePointName);
            var minorPointNames = CollectionUtils.toArrayList(minorPoints, ProblemConverter.Instance::toKnowledgePointName);
            for (var point : primaryPointNames) {
                point.setPrimary(true);
            }
            for (var point : minorPointNames) {
                point.setPrimary(true);
            }
            reportProblem.setPrimaryKnowledgePointNames(primaryPointNames);
            reportProblem.setMinorKnowledgePointNames(minorPointNames);

            report.getReportProblems().add(reportProblem);
        }

        var duration = Duration.between(sheet.getBeginTime(), sheet.getEndTime());
        long spendSeconds = duration.toSeconds();
        int rate = (int) Math.rint((correctCount * 100 / problemCounter));
        report.setAccuracyRate(rate);

        report.setAssignmentDetailId(task.getDetailId());
        report.setAssignmentId(task.getAssignmentId());
        report.setSetId(task.getSetId());
        report.setTotalSpendSeconds(spendSeconds);
        report.setTaskId(task.getTaskId());
        report.setTotalProblemQuantity(problemCounter);
        report.setCorrectProblemQuantity(correctCount);
        report.setWrongProblemQuantity(wrongCount);
        report.setSkipProblemQuantity(skipCount);
        report.setUserId(task.getUserId());
        report.setTotalGainScore(totalGainScore);
        report.setSetName(task.getSetName());
        if (set.getSetTypeId() == SetTypes.PRE_EXAM.getValue() || set.getSetTypeId() == SetTypes.EXAM.getValue()) {
            var award = getAward(task.getSetName(), totalGainScore);
            report.setAward(award);
        }
        return report;
    }

    private String getAward(String setName, double totalScore) {
        if (setName.contains("AMC 8")) {
            double percentOnePoints = 22;
            double percentFivePoints = 17;
            double percentQuarterPoints = 12;
            double percentHalfPoints = 9;
            double percentThreeQuarterPoints = 7;

            if (totalScore >= percentOnePoints)
                return "Distinguished Honor Roll";
            else if (totalScore >= percentFivePoints) {
                return "Honor Roll";
            } else if (totalScore >= percentQuarterPoints)
                return "Top 25% Score";
            else if (totalScore >= percentHalfPoints)
                return "Top 50% Score";
            else if (totalScore >= percentThreeQuarterPoints)
                return "Top 75% Score";
            return "Participation";
        }

        if (setName.contains("AMC 10")) {
            double percentOnePoints = 126;
            double percentFivePoints = 111;
            double percentQuarterPoints = 80;
            double percentHalfPoints = 63;
            double percentThreeQuarterPoints = 50;

            if (totalScore >= percentOnePoints)
                return "Distinguished Honor Roll";
            else if (totalScore >= percentFivePoints) {
                return "Honor Roll";
            } else if (totalScore >= percentQuarterPoints)
                return "Top 25% Score";
            else if (totalScore >= percentHalfPoints)
                return "Top 50% Score";
            else if (totalScore >= percentThreeQuarterPoints)
                return "Top 75% Score";
            return "Participation";
        }

        if (setName.contains("AMC 12")) {
            double percentOnePoints = 120;
            double percentFivePoints = 94;
            double percentQuarterPoints = 74;
            double percentHalfPoints = 61;
            double percentThreeQuarterPoints = 49;

            if (totalScore >= percentOnePoints)
                return "Distinguished Honor Roll";
            else if (totalScore >= percentFivePoints) {
                return "Honor Roll";
            } else if (totalScore >= percentQuarterPoints)
                return "Top 25% Score";
            else if (totalScore >= percentHalfPoints)
                return "Top 50% Score";
            else if (totalScore >= percentThreeQuarterPoints)
                return "Top 75% Score";
            return "Participation";
        }
        return "";
    }


    @HandleServiceException
    public int getIncorrectProblemQuantity(@Min(0) long taskId) {
        var report = getReportByTask(taskId);
        return report.getTotalProblemQuantity() - report.getCorrectProblemQuantity();
    }

    @HandleServiceException
    public Collection<ProblemKnowledgePoint> getPointsByIncorrectProblemsInReport(@Min(0) long taskId) {
        var report = getReportByTask(taskId);
        if (report == null) return new ArrayList<>();
        var problemIds = report.getReportProblems().stream()
                .filter(rp -> !rp.isCorrect()).map(ReportProblem::getProblemId).collect(Collectors.toList());
        return tagGateway.getKnowledgePointsByProblems(problemIds);
    }
}
